home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
The Datafile PD-CD 1 Issue 2
/
PDCD-1 - Issue 02.iso
/
_utilities
/
utilities
/
003
/
motorola
/
Examples
/
as11
/
MCX
< prev
next >
Wrap
Text File
|
1993-06-13
|
74KB
|
1,171 lines
*MCX.AS
*******************************************************************************
* *
* MCX11 REAL-TIME KERNEL *
* FOR THE MC68HC11 *
* *
* by for support contact *
* Tom Barrett Mike Wood M/D OE319 *
* A.T. Barrett & Associates Motorola,Inc *
* Houston, Texas 6501 William Cannon Dr West *
* Austin, Texas 78735 *
* (713)728-9688 (512)891-2717 *
* *
*******************************************************************************
* N O T I C E *
*******************************************************************************
* *
* This product is distributed without charge to users via the MOTOROLA *
* FREEWARE Bulletin Board. The product is provided "as is" without warranty *
* of any kind, either expressed or implied, including, but not limited to any *
* warranties of merchantability and fitness for a particular purpose. All *
* risks of using this product including the entire costs of any necesary rem- *
* edies are those of the user and MOTOROLA assumes no liability of any kind. *
* *
*******************************************************************************
*******************************************************************************
* R E V I S I O N H I S T O R Y
* Release 1
*
* Rev # Date Modifications/Corrections for new Revision
* ----- --------- ------------------------------------------------------
* 1.1 5/30/89 1. Change calling sequence of .deque. so that the return
* value is in ACCA if entry size is 1 byte.
*
* 2. Changed calling sequence of .enque. so that if the size
* of the entry is 1 byte, it is passed to .enque. in bits
* 8-15 of IX. If the size of the entry is 2 bytes, IX8-15
* contains byte 1 while IX0-7 contains byte 2.
*
* 11/1/89 3. Corrected incorrect jump destination label in .timer.
* from .pend to dopend
*
* 4. Corrected incorrect branch in .send. causing messages to
* be put into a receiver's mailbox in improper order.
*
* 5. Cleared intlvl during initialization.
*
* 1.2 1/12/90 1. Changed the exit logic in .recv. when there is no msg
* waiting in the mailbox. This corrects a problem which
* would leave the task semaphore of the receiver task in
* a WAIT state. Since the .send. ESR does not signal a
* semaphore but simply unblocks the receiving task, it is
* not necessary to manipulate the task's semaphore. The
* original code is deleted and replaced with a simple
* set which backs up the PC and exits.
*
* 2. Moved the code for the backup PC routine and made it an
* internal part of the .deque. function
*
* 1.3 3/23/90 1. Disabled interrupts for the endfast routine in isrrtn
* which protects this critical path from being interrupted
* after intlvl is decremented. If this were to happen
* multiple ISRs could try to restore a task's state.
*
* 2. Removed a hanging push in .signal. that was not poped
* before returning if the semaphore was not in the wait
* state. This would only be a problem if an ISR were
* returning to the Dispatcher or another ISR.
*
* 3. Moved the sei and cli in .signal. to tighten up the
* critical code and improve interrupt response time.
*
* 4. Moved the sei in .wait. to protect the Critical Code
* there, and deleted the cli since immrtn is CC also.
* This corrects several problems with tasks that wait on
* semaphores from interrupts.
* NOTE: to shorten interrupt latency here you might
* move the cli to after setting the wait and then jump
* to endtsk, the tradeoff is another stacked interrupt.
*
* 5. Changed send and receive to use the _RCVWAT status bit
* as was origenally intended instead of _WAIT. This
* Corrects the problem where sending a message to a task
* that is waiting on any semaphore causes it to wake up
* as if that semaphore had been signaled.
*
* 6. Added an sei to .deque. to protect some critical code
* in .wait. (see 3 above)
*
* 7. In .enque. moved the loading of IY with queue data to
* later in the routine closer to where it is actualy used.
* This fixes the problem where the backup routine would
* subtract 2 from a word other than the tasks PC which
* was stored on its stack.
*
* 8. Minor things realy, shaved off a clock cycle in dispch's
* tight loop, and used the _PEND symbol in .pend.
*
* 1.4 12/7/90 1. Changed cmpb to cmpa in task 5 of TEST.AS. Dequeue was
* changed to return the value in the A accumulator in V1.1.
*
* 2. Fixed a problem with nested interrupt. If you had a
* nested interrupt that did not require a context switch
* followed by an interrupt that did require a context
* switch the stack became corrupted and the system would
* crash. Eliminated endfast routine.
*
* 3. Fixed a problem with purging the timers. Once a timer
* was purged the next timer in the link was not lengthed
* the amount of time of the purged timer.
*
* 4. Fixed a problem with cyclic timers in CLKDRIVER.AS.
*
* 1.5 1/24/91 1. Fixed a problem that I introduced in 1.4 in purge of
* timer. Messed with y index register and didn't restore.
*******************************************************************************
*******************************************************************************
* OPT nol Remove asterisk if you don't want the listing
*******************************************************************************
* *
* MCX-11 VARIABLES *
* (These Will Reside in RAM) *
* *
*******************************************************************************
************************ Filled in by Initialization **************************
tickcnt equ MCXVAR Tick counter
FREE equ tickcnt+1 Address of first free timer block
ACTIVE equ FREE+2 Address of first active timer in list
*******************************************************************************
curtsk equ ACTIVE+2 Current task (i.e. the active task)
curtcb equ curtsk+1 Address of current task's TCB
hipri equ curtcb+2 Highest priority task ready to run
pritcb equ hipri+1 Address of TCB of highest priority task
* ready to run (see hipri)
intlvl equ pritcb+2 Depth of nested interrupts:
* = 0 when in a task
* = >0 when interrupts are nested or
* in the kernel
temp equ intlvl+1 Temporary area
width equ temp+2 Work area for queue width
depth equ width+1 Work area for queue depth
notmt equ depth+1 Work area for queue not empty semaphore
*******************************************************************************
* TASK STATE EQUATES *
*******************************************************************************
_SUSPND equ $80 SUSPENDed status
_WAIT equ $40 WAITing status
_RCVWAT equ $20 Waiting status for message RECEIVE
_IDLE equ $01 Task IDLE status
*******************************************************************************
* MESSAGE EQUATES *
*******************************************************************************
MLINK equ 0 Message link pointer
MTASK equ 2 Message's sending task
MSEMA equ 3 Message semaphore
MBODY equ 4 Start of message body
*******************************************************************************
* TIMER BLOCK EQUATES *
*******************************************************************************
CLINK equ 0 Timer block link pointer
CTOCKS equ 2 Clock tocks in timer
CRESET equ 4 Reset timer
CTASK equ 6 Task waiting on timer
CSEMA equ 7 Semaphore number
*******************************************************************************
* QUEUE HEADER EQUATES *
*******************************************************************************
CURSIZ equ 0 Current size of queue (# of entries)
PIX equ 1 Put Index
QSEMA equ 2 Active semaphore: NOTMT or NOTFUL (=NOTMT+1)
*******************************************************************************
* TCB LAYOUT *
*******************************************************************************
*
STATE equ 0 Byte 0 Task status:
* bit 7: Suspended
* bit 6: Waiting for an event
* bit 5: Receive wait
* bit 4: - Reserved -
* bit 3: - Reserved -
* bit 2: - Reserved -
* bit 1: - Reserved -
* bit 0: Task not in use
ACTSP equ 1 Byte 1-2 Active Stack Pointer for task
MSGTHRD equ 3 Byte 3-4 Message thread pointer
*******************************************************************************
* STACK CONTEXT EQUATES *
* (These values represent offsets from the Top-of-Stack +1) *
*******************************************************************************
CCR equ 0 Condition Code Register
ACCB equ 1 Accumulator B
ACCA equ 2 Accumulator A
IX equ 3 Index Register X
IXH equ 3 Index Register X (High Byte)
IXL equ 4 Index Register X (Low Byte)
IY equ 5 Index Register Y
PC equ 7 Program Counter
*******************************************************************************
* MISCELLANEOUS EQUATES *
*******************************************************************************
_PEND equ 1 PENDing state
PAGE
*******************************************************************************
* *
* MCX11 TASK DISPATCHER *
* *
*******************************************************************************
* *
* This is the code that looks for the highest priority task that is in a RUN *
* state. The tsk's state is contained in the Task Control Block (TCB). The *
* job of the Dispatcher is to find the task having the highest priority and *
* is ready to take control, i.e., run. This is a tight loop because every *
* time a task gives up control, except for a pre-emption, the Dispatcher is *
* called to find the next task to run. *
* *
*******************************************************************************
dispch clra Use acc A for the task number
ldx #STATLS-TCBLEN Set up base address of TCB list
next clr curtsk Set current task # to 0
lds #SYSTACK Load the system stack pointer address
ldab #$7F Load constant 127 for presetting
stab hipri the task # of the highest priority task
* - 1 is the highest priority
* - 127 is the lowest priority
ldab #TCBLEN Load a constant for TCB length attribute
cli Enable interrupts
testat inca Bump the task number by 1
abx Get address of next TCB
*** (V1.3 #8) ***
tst STATE,x See if all bits in task's status are 0
bne testat If not, go check the next task.
switch sei Task is ready to run. disable interrupts
staa hipri Save task number as highest priority
stx pritcb Save TCB address as that of hipri task
setcur staa curtsk also save acc A as current task number
stx curtcb Save X-reg as address of curtsk's TCB
rtncur lds ACTSP,x Load task's stack pointer from TCB
intrtn rti Go to task via an interrupt return. The
* task's context is on the task's stack.
* This treats the context switch as if it
* is an interrupt (it is likely to be).
*******************************************************************************
* *
* MCX11 COMMON INTERRUPT SERVICE RETURN *
* *
*******************************************************************************
* *
* This is the code that ALL interrupt service routines (ISR) MUST return thru *
* to complete the action of the interrupt. The ISR must have saved the MPU *
* context, incremented the nested interrupt level (intlvl), and enabled other *
* interrupts before actually servicing the interrupt. Upon completion, the *
* ISR, should branch to this common interrupt return routine. The B accumu- *
* lator should contain a semaphore number or a zero. The former value *
* indicates that an event has occurred that requires action at the task level *
* and the latter indicates thee is no further action required. *
* *
* When ACCB contains a semaphore number, the semaphore is SIGNALed to inform *
* any task associated with the event that it has occurred. A context switch *
* will occur if the task in a WAIT state for that event is then ready to run *
* and is of higher priority than the interrupted,i.e. current, task, and the *
* level of nested interrupts is 1. A context switch will not occur if ALL of *
* these conditions are not true. If the B accumulator contains a zero upon *
* return from the ISR, there is an immediate return to the interrupted task *
* if the level of nested interrupts is 1 or a return to the executive or to *
* an interrupted ISR if the level of nested interrupts is >1. *
* *
*******************************************************************************
*
isrrtn tstb Test ACCB for content
bne signal2 If ACCB != 0, go signal the event.
* If ACCB = 0, return to point of interrupt
*
*** (V1.4 #2) ***
bra endtsk Eliminated endfast routine. |
.signal tab *** PUT IN COMMENT BLOCK ***
signal2
ldx #FLGTBL-1 Got a semaphore number. Load address-1 of
abx semaphore table and add semaphore # to it
clrb Clear ACCB so that we will have a zero
sei Turn off interrupts *** (V1.3 #2) ***
ldaa 0,x Get the content of the semaphore
bpl nowait Branch if semaphore not in WAIT state
incb If WAITing, set it to PENDing
nowait stab 0,x If PENDing or DONE, set to DONE
tab Look at original content of semaphore
*** (V1.4 #2) ***
bpl immrtn Return immediately if semaphore not WAITing|
cli End of critical code *** (V1.3 #2) ***
negb If WAITing, 2's complement = task # WAITing
* for the event.
*** (V1.3 #3) ***
ldaa #-_WAIT-1 Store the 1's complement of the WAIT
clrstat pshb Save the task number.
psha Then save the mask again.
ldaa #TCBLEN Use the task number to compute the address
mul of its TCB
addd #STATLS-TCBLEN
xgdx Put the TCB address in IX register
pula Get the mask of bits to clear.
clrbits sei
anda 0,x Clear the byte as given by the mask.
staa 0,x Save the updated TCB status byte.
pula Get the task number again.
bne immrtn Branch to immediate exit if task's status
* indicates it is not ready to run (!=0).
cmpa hipri If it is ready to run, is it of higher
* priority than the current highest
* priority task which is ready to run?
bge immrtn Branch if task priority < hipri.
sta hipri If so, then make it the new hipri task.
stx pritcb Save its TCB address too.
endtsk sei Disable interrupts
immrtn dec intlvl Decrement the interrupt level.
bne intrtn If it is >0, then the interrupt occurred
* while we were doing system operations.
ldaa curtsk See if the current task is 0.
bne notdisp If not, branch.
tsx If so, the MCX11 Dispatcher was interrupted
* See what task was being checked when the
* interrupt occurred. The context of the
* Dispatcher is found on the system stack.
ldaa ACCA,x Get the content of ACCA from the stack as
* it holds the value of the "current task"
* being used by the Dispatcher.
cmpa hipri Compare it to hipri task number.
ble intrtn The "current task" is of higher priority.
notdisp ldx pritcb Get the hipri task's TCB address
ldaa hipri and set up the hipri task number.
brclr STATE,x $FF setcur If task is ready to run, go to it
bra next Otherwise, look for next lower priority
* task that is ready to run.
*******************************************************************************
* *
* MCX11 EXECUTIVE SERVICES DISPATCHER *
* *
*******************************************************************************
* *
* The Executive Services Dispatcher is the heart of MCX11. This is the point *
* to which all Executive Service Requests (ESR) are vectored. The ESR Dis- *
* patcher takes the function code of the ESR requested by the task and uses *
* it as an index into a jump table to the various ESR functions. This code is *
* entered by a SWI instruction so it must be treated as a special kind of *
* interrupt. The nested interrupt level is always =0 upon entry because the *
* ESR is coming from a task. The ESR function is always found at the byte *
* immediately following the SWI. *
* *
*******************************************************************************
mcxsrv inc intlvl Increment the interrupt nest level
ldx curtcb
sts ACTSP,x Save SP in TCB of current task
tsy Save Top-of-Stack (TOS) in IY. This will
* actually point to the CCR byte
lds #SYSTACK Load SP with address of system stack
cli Now interrupts are turned on
ldx PC,y Set up pointer to the ESR function
ldab 0,x Get the ESR function code
inx Bump the return address by 1
stx PC,y Save the return address
aslb Multiply the ESR function code by 2
ldx #jtable Load address of the ESR jump table
abx Add function code*2 to base address of
* jump table to get ESR vector
ldx 0,x Load the ESR vector into IX
ldd ACCB,y Load up ACCA and ACCB with the arguments
* as passed by the calling task but
* switch ACCA and ACCB contents.
*******************************************************************************
* *
* At this point the processor context is as follows: *
* *
* CCR conditions wrt contents of ACCB & ACCA (don't use) *
* ACCA ACCB as passed from calling task (ESR dependent) *
* ACCB ACCA as passed from calling task (ESR dependent) *
* IX Address of the ESR function *
* IY Address of task's Top-of-Stack (CCR byte) *
* SP Base of System Stack *
* *
* In addition, the nested interrupt level is = 1, curtsk and hipri are set *
* to the number of the calling task, and curtcb and hipri both contain the *
* address of the current task's TCB. *
* *
*******************************************************************************
jmp 0,x Go to the ESR function
*******************************************************************************
* *
* ESR JUMP TABLE *
* *
*******************************************************************************
jtable FDB endtsk 0 = Immediate return (NOP)
FDB .wait 1 = Wait for an event
FDB .signal 2 = Signal a semaphore
FDB .pend 3 = Set a semaphore to PENDing state
FDB .send 4 = Send a task a message without waiting
FDB .sendw 5 = Send a task a message with wait
FDB .recv 6 = Receive a message
FDB .deque 7 = Dequeue an entry from a FIFO queue
FDB .enque 8 = Enqueue an entry into a FIFO queue
FDB .resume 9 = Resume a task in suspension
FDB .suspnd 10 = Suspend a task's operation
FDB .termn8 11 = Terminate a task's operaton
FDB .xeqt 12 = Execute a task
FDB .delay 13 = Delay a task for a period of time
FDB .timer 14 = Set up a timer
FDB .purge 15 = Remove a given timer from active state
* ESR1
*******************************************************************************
* *
* WAIT for an event to occur. This ESR is used to place the calling task into *
* a WAIT state where it will remain until the event happens. If the event has *
* already occurred, the wait does not happen and control is immediately re- *
* turned to the calling task. WAIT is always associated with a semaphore. The *
* semaphore can be explicitly stated or it can be implicit, i.e. the task's *
* semaphore. Regardless of how the semaphore is defined in the ESR, the sema- *
* phore must be in the PENDing state if the WAIT is to occur. The process *
* handling the signaling of the event when it occurs must have prior know- *
* ledge that the event uses a particular semaphore. Only in this way can the *
* waiting task and the signalling task be connected. *
* *
* calling sequence: *
* *
* ACCB = semaphore number *
* swi *
* FCB .wait. *
* return: all registers unchanged *
* *
*******************************************************************************
.wait tab Test the semaphore number
bne wait2 If it is defined, go set up the wait
ldab curtsk Otherwise use the task's semaphore
addb #NNAMSEM
wait2 ldx #FLGTBL-1
abx Compute address of the semaphore
sei Start of critical code *** (V1.3 #4) ***
tst 0,x Test the semaphore contents
bmi waitt Branch if already in the WAIT state
* This is a safety net, if you should unin-
* tentionally have more than one task wait-
* ing on a semaphonre at a time all but the
* first will not wake up unless they are re-
* exicuted.
beq pendset If DONE, go reset to PENDing and return
force ldaa curtsk If state of semaphore is PENDing, the
nega event has not yet occurred so
staa 0,x Set the semaphore to WAIT on current task.
waitt ldx curtcb Get current task's TCB address
bset STATE,x _WAIT Set the WAIT state in task's status byte.
*** (V1.3 #4) ***
jmp immrtn Critical code ends at actual return to task
*******************************************************************************
* *
* Force a semaphore to a PENDing state. This function should be used to make *
* sure that a semaphore is in the correct state if there is some doubt. A *
* semaphore must be in a PENDing state before it can transition to a WAITing *
* state. All semaphores should be initialized to a PENDing state at the time *
* of system initialization. After that, they are generally maintained by the *
* .wait and .signal functions. However, it may become necessary at some time *
* to set a given semaphore PENDing to insure that the event is recognized as *
* having yet occurred. *
* *
* calling sequence: *
* *
* ACCB = semaphore number *
* swi *
* FCB .pend. *
* return: all registers unchanged *
* *
*******************************************************************************
.pend tab Put semaphore number in ACCB
bne dopend Branch if semaphore is defined.
ldab curtsk Else use task semaphore.
addb #NNAMSEM
dopend ldx #FLGTBL-1 Compute the address of the given semaphore.
abx
pendset ldaa #_PEND Set the semaphore to pending
sei
staa 0,x
jmp immrtn All done.
*******************************************************************************
* *
* Send a message and wait for reply. This function is the same as the .send. *
* function except that it puts the sender into a Wait state until the task *
* receiving the message signals the semaphore to indicate it is finished. *
* *
* calling sequence: *
* *
* ACCA = Task number of the receiver *
* ACCB = Semaphore number *
* IX = Message address *
* swi *
* FCB .sendw. *
* return: Registers are unchanged *
* ACCB will = Current task # if the semaphore *
* number was = 0 at the time of the ESR *
* *
*******************************************************************************
.sendw clrb Set switch = 0 for .sendw.
*******************************************************************************
* *
* Send a message. This function sends a message but does not wait for a reply *
* to be returned from the receiver. There is a semaphore associated with the *
* message but it is set to a PENDing state. The Sending task may perform a *
* .wait. on that semaphore at a later time if necessary. Neither .send. nor *
* .sendw. moves data from the sender to the receiver. Instead, only the *
* pointer to the message is moved. Each task has a threaded list of messages *
* which it can receive. The threaded list entry is the pointer to the message.*
* The message pointer is inserted into the threaded list in the order of the *
* sending task's priority. *
* *
* calling sequence: *
* *
* ACCA = Task number of the receiver *
* ACCB = Semaphore number *
* IX = Message address *
* swi *
* FCB .send. *
* return: Registers are unchanged *
* ACCB will = Task semaphore # if ACCB was = 0 *
* at the time of the ESR. *
* *
*******************************************************************************
.send pshb Save the switch. Switch > 0 for .send
ldab ACCB,y Get the semaphore number for the message.
bne havsema Branch if semaphore # > 0.
ldab curtsk If semaphore # = 0, use task semaphore.
addb #NNAMSEM
stab ACCB,y
havsema ldx #FLGTBL-1
abx Compute address of the semaphore.
pulb Get the send switch.
tstb Test switch.
bne notw Branch if switch set for no waiting.
ldaa curtsk
nega If switch set for wait, set -task #.
notw staa 0,x Set the semaphore to proper value.
ldx curtcb Get address of current task's TCB.
tstb Test the switch again.
bne waitnot
bset STATE,x _WAIT Set the sender's status to WAITing.
waitnot ldaa ACCA,y Get the receiving task #.
psha Save it for later use, too.
ldab #TCBLEN
mul Compute TCB address of receiving task.
addd #STATLS-TCBLEN
xgdx
pshx Save TCB address for later use.
ldab #MSGTHRD
abx
ldaa ACCB,y Get the semaphore number
ldy IX,y Load IY with message address.
staa MSEMA,y Set up semaphore # in message header.
ldaa curtsk
staa MTASK,y Set up current task as sender.
pshy Save address of the message header.
nsrtlp ldy MLINK,x Look at the receiver's next message address.
beq insert If pointer = 0, End-of=List. Go insert here.
cmpa MTASK,y If there is a message on the thread, look at
* the task number of its sender and compare
* it to the sender's task number for this
* new message.
*
blo insert If the new message's sender has a priority |
* higher than that of the threaded message,|
* go insert the new message pointer in front
* of the one on the list. (V1.1 #4) |
ldx MLINK,x Otherwise, walk the thread.
bra nsrtlp Keep looking for a place to insert new msg.
insert xgdy Put address of next nessage in ACCD.
puly Get address of new message's header.
std MLINK,y Link new message into threrad.
xgdy
std MLINK,x
pulx Get address of TCB of receiver again.
***(V1.3 #5)***
ldaa #-_RCVWAT-1
jmp clrbits Go clear the receiver wait, if any.
*******************************************************************************
* *
* Receive a message. This function is used by a receiving task to get the *
* next message from the task's message thread. Normally, the next message is *
* pointed to by the content of the message thread pointer in the task's TCB. *
* However, it is also possible to get the next message sent from a given task *
* to the receiver task. By this technique, the receiver may effectively ig- *
* nore other senders and reserve all activity for the one sending task. This *
* is useful in designing server tasks which need to insure the dedicated use *
* of a given resource. *
* *
* calling sequence: *
* *
* ACCA = task number (Otherwise this must be 0) *
* swi *
* FCB .recv. *
* return: IX contains the address of the received message *
* All other registers unchanged *
* *
*******************************************************************************
.recv ldx curtcb Set up to see if there is a special task
tstb ACCB holds the task number or 0.
bne special If ACCB != 0, it contains the task number.
ldd MSGTHRD,x If not given task #, get next message.
beq waitrcv See if message thread is null. (=0)
std IX,y It's not. We have a message. Save address.
xgdy
ldd 0,y Then unlink the message from the thread.
std MSGTHRD,x
jmp endtsk Wrap it up.
*** (V1.3 #5) ***
waitrcv bset STATE,x _RCVWAT If no message available, set up a RCVWAT
*** (V1.2 #1) ***
ldd PC,y Then back up the PC in the stacked |
subd #2 context by 2 bytes |
std PC,y |
jmp endtsk Go to wrap up routine |
special pshy Save address of the task's stack context.
ldab #MSGTHRD ACCB still contains the task number.
abx Adjust IX to point to message thread pntr.
ldab ACCA,y Get the task number again.
rcvloop ldy 0,x Get the address of the next message header.
beq waitrcv If no message available, go wait for one.
cmpb MTASK,y Compare the sender's task number to special
beq rcvgo task number. If a match, go to it.
blt waitrcv If special task number < sender's, wait.
ldx 0,x If special task number > special, set up IX
bra rcvloop to point to current message header and
* then go check next message.
rcvgo xgdy We have a message. Put its address in ACCD.
puly Then set up IY to point to the stack context
std IX,y Store the message address in the IX register
* of the receiving task's stack context.
xgdy Then put the pointer back into IY.
ldd 0,y Unlink the message from the thread.
std 0,x
jmp endtsk Go wrap it up.
*******************************************************************************
* *
* Dequeue an element from a FIFO queue. This function is used to remove an *
* element from a FIFO (First in- First out) queue. The queue is divided into *
* two parts, the queue header and the queue body. The queue header contains *
* all of the information about the state of the queue body. An attempt to re- *
* move an entry from an empty queue will place the caller into a wait state. *
* When the queue has something put into it, the waiting task will be resumed. *
* Queue entries can be of any size but each queue handles only one size of *
* entry. *
* *
* calling sequence: *
* *
* ACCA = Queue number *
* IX = Destination address of dequeued entry of *
* size > 2 bytes. Otherwise ignored. *
* swi *
* FCB .deque. *
* return: For an entry size of 1 byte: *
* ACCA = dequeued entry *
* For an entry size of 2 bytes: *
* ACCA = byte 1 of entry *
* ACCB = byte 2 of entry *
* Other registers unchanged *
* *
*******************************************************************************
.deque bsr qsetup Set up the queue header information
* The queue depth and width are moved to
* RAM work area as is the Not Empty sema-
* phore. The address of the queue body is
* in RAM at location "temp".
ldab CURSIZ,x Check current size of queue to see if empty.
beq qempty Branch if queue is empty.
pshx Save the queue header address.
ldaa PIX,x Not empty. Get the Put Index and subtract
sba the Current Size from it.
bhs nowrap Check for wrap around.
adda depth If so, add Depth of queue.
nowrap ldab width ACCA = Get Index. Load ACCB with Width of
pshb an entry. Also save the width for later.
dec CURSIZ,x Decrement the Current Size of the queue by 1
mul Now multiply the Width times the Get Index
addd temp and add the base address of the queue body
xgdx to get address of the entry to dequeue.
pula Get the Width again
cmpa #2 Is it > 2 bytes?
bgt dqgt2 Branch if width > 2 bytes.
bge dq2 See if Width = 2 bytes and branch if so.
ldab 0,x Width = 1 byte. Get it.
**(V1.1 #1)**
stab ACCA,y Store it in ACCA of the stack context. |
bra dqend That's it.
dq2 ldab 0,x For Width = 2 bytes, get the 1st byte
stab ACCA,y and store it in ACCA of stack context.
ldab 1,x Then get the 2nd byte and store in in ACCB
stab ACCB,y of the stack context.
bra dqend And that's that for size = 2.
dqgt2 ldy IX,y For Width > 2 bytes, get the destination
* address from IX of the stack context.
dqloop ldab 0,x Get next byte from queue body.
stab 0,y Move it to next byte of the destination.
inx
iny
deca Decrement the Width count.
bne dqloop Loop until count is = 0.
dqend pulx When done, get the queue header address.
ldab QSEMA,x Get the Queue NOT FULL semaphore #
beq nopost See if anyone is waiting for NOT FULL.
postem clra There is. Go signal the semaphore.
staa QSEMA,x But first, set the active semaphore # = 0
jmp signal2
qempty addb notmt Queue is empty. Set up a wait state for this
stab QSEMA,x task on the Queue NOT EMPTY semaphore
** (V1.2 #2) **
backup pshb Save the semaphore number |
ldd PC,y Here we have to back up the PC by 2 bytes |
subd #2 so that when the task restarts, it will |
* be at the SWI used to call MCX11. |
std PC,y Save the decremented PC |
pulb Get the semaphore number. |
ldx #FLGTBL-1 |
abx Calculate address of semaphore. |
*** (V1.3 #6) ***
sei Prepare to enter critical code
jmp force Go set the wait state and clean up. |
qsetup clra
aslb Multiply the queue number by the length
pshb Save queue number * 2.
addb ACCA,y of the queue initialization data block.
aslb
addd #QUEDATA-QUEDATL Then add the address of the queue init data
xgdx
pulb Get queue number * 2 and add the number of
addb #NNAMSEM+NTASKS-1 named semaphores + the number of tasks
stab notmt to get the NOT EMPTY semaphore #.
ldd WIDTH,x Get the width and depth of the queue
std width Store them in the work area of RAM.
ldd QADDR,x Get the queue body address
std temp Store it in temp word in RAM
ldx QHADR,x Get the Queue Header address
rts Return
*******************************************************************************
* *
* Enqueue an element into a FIFO queue. This function is used to insert an *
* element into a FIFO (First in- First out) queue. The queue is divided into *
* two parts, the queue header and the queue body. The queue header contains *
* all of the information about the state of the queue body. An attempt to in- *
* sert an entry into a full queue will place the caller into a wait state. *
* When the queue has something taken out of it, the waiting task will be *
* resumed. Queue entries can be of any size but each queue handles only one *
* size of entry. *
* *
* calling sequence: *
* *
* ACCA = Queue number *
* IX = Source address of entry to be enqueued if *
* size > 2 bytes. *
* Or, IX8-15 = byte to be enqueued if size = 1 *
* Or, IX8-15 = byte 1 and IX0-7 = byte 2 if size = 2 *
* swi *
* FCB .enque. *
* return: Registers are unchanged *
* *
*******************************************************************************
.enque bsr qsetup Set up the queue header information
ldaa depth Compare DEPTH of queue to its Current Size
cmpa CURSIZ,x to see if it is full.
beq full Branch if queue is full.
pshx Save address of the queue header.
cmpa PIX,x Compare the depth to the Put Index to see
bgt wrapnot if there is an address wrap around.
clra
staa PIX,x If a wrap around, reset Put Index to 0.
wrapnot ldaa width Then use the width and the Put index to
psha compute the destination address in the
ldab PIX,x queue body.
mul
addd temp
inc PIX,x Bump the Put Index
inc CURSIZ,x Bump the Current Size
*** (V1.3 #7) ***
ldy IX,y Get data to be enqueued or pointer to it.
xgdx Move destination address into IX.
pula Get the Width again.
cmpa #2 See if Width > 2 bytes.
bgt nqloop Branch if Width > 2 bytes
xgdy If not, the data is in IY. Put it in ACCD.
beq nq2 Branch if Width = 2 bytes.
** (V1.1 #2) **
staa 0,x Width is 1 byte. Store it from IX8-15.
bra nqend Then go to wrap up routine.
nq2 std 0,x For Width of 2 bytes, store from IX0-15.
bra nqend
nqloop ldab 0,y For Width > 2 bytes, move a byte from the
stab 0,x source address to the destination address.
inx
iny
deca Decrement the counter in ACCA.
bne nqloop Loop until done.
nqend pulx When all done, get the address of the queue
ldab QSEMA,x header, and test for a waiter on queue
* Not Empty.
bne postem If there is a waiting task, go resume it.
nopost jmp endtsk If not, just go back to caller.
full ldab #1 If queue is full, set up a wait on it.
* Queue Not Full semaphore = Not Empty + 1.
bra qempty Go set up the Wait condition.
*******************************************************************************
* *
* Resume a task. This is the opposite function of .suspnd.. The SUSPEND state *
* is removed from the specified task. If the removal of the suspension makes *
* the task runnable, it is scheduled. If it is of higher priority than the *
* resuming task, a context switch will occur. Note that the task to be *
* resumed must be explicitly specified by the content of ACCA. A task may not *
* resume itself, obviously. *
* *
* calling sequence: *
* *
* ACCA = task number *
* swi *
* FCB .resume. *
* return: All registers unchanged *
* *
*******************************************************************************
.resume ldaa #-_SUSPND-1
jmp clrstat Go clear the task's suspend status.
*******************************************************************************
* *
* Suspend a task. This function is used to set a task into the SUSPEND state. *
* Once in this state, it cannot be made runnable again except by a .resume. *
* ESR or an .execute. ESR. The task to be suspended is specified by putting *
* its task number in ACCA. If the current task is suspendeing itself, ACCA *
* is set to either the task number, if known, or more simply, a 0. *
* *
* calling sequence: *
* *
* ACCA = task number (Otherwise this must be 0) *
* swi *
* FCB .suspend. *
* return: All registers unchanged *
* *
*******************************************************************************
.suspnd tstb
bne notcur See if the task to suspend is SELF.
ldx curtcb It is. Load TCB address.
bra setspnd
notcur ldaa #TCBLEN If not current task, compute TCB address
mul of the specified task.
addd #STATLS-TCBLEN
xgdx
setspnd bset STATE,x _SUSPND Set the SUSPEND state in task's status byte.
jmp endtsk
*******************************************************************************
* *
* Terminate a task. This is a seldom used task but is included for complete- *
* ness. This function is called when it is desireable to terminate a task. It *
* may be any task in the system including the caller. If the caller is going *
* to commit suicide, the given task number is set to 0. As a result of this *
* ESR, all timers which are in process for the terminated task are purged *
* the active timer list. The terminated task is set to an IDLE state. Once *
* terminated, a task may be restarted only by the .xeqt. command. *
* *
* calling sequence: *
* *
* ACCA = Task number (0 if self) *
* swi *
* FCB .terminate. *
* return only if not terminating self. Registers unchanged *
* *
*******************************************************************************
.termn8 tstb Test for self.
bne selfnot Branch if task to terminate is not self.
ldab curtsk If self, get current task number.
ldx curtcb Then set up current task's TCB address.
stab temp+1 Store in lower half of temp
bra setidle Then go set up the IDLE state.
selfnot stab temp+1 Store task # in lower half of temp.
ldaa #TCBLEN
mul Compute TCB address of task to be terminated
addd #STATLS-TCBLEN
xgdx
setidle bset STATE,x _IDLE Set task's state to IDLE
clra
jmp beginp Then go purge all the task's timers.
*******************************************************************************
* *
* Execute a task. This function is used to put a task into the state of being *
* ready to run. Its TCB is initialized so that its stack pointer is set to *
* the base address assigned to it. A stack frame is allocated on the task's *
* stack for an initial context. That context is set to contain the task's *
* starting address as the PC, while CCR, ACCB, and ACCA are cleared. *
* *
* calling sequence: *
* *
* ACCA = task number (cannot be SELF) *
* swi *
* FCB .execute. *
* return: All registers unchanged *
* *
*******************************************************************************
.xeqt pshb Save the task number
ldaa #TCBDATL Load length of TCB init data block
mul Multiply it by task number.
addd #TCBDATA-TCBDATL
xgdx
ldd RSTSP,x Get the base address of task's stack space.
subd #9
xgdy
ldd STRTADR,x Set up PC to be the starting address
ldx TCBADDR,x
std PC+1,y
clra
clrb
staa CCR+1,y Clear CCR
std ACCB+1,y Clear ACCB and ACCA
xgdy Put address of stack context back into D
std ACTSP,x Store address of the context in the TCB.
clra
jmp clrbits Go make the task runnable.
*******************************************************************************
* *
* Delay a task for a period of time. This function uses the .timer. ESR *
* to set up a timer and a wait state on the specified task. When the timer *
* elapses, the specified task is resumed. *
* *
* calling sequence: *
* *
* ACCA = Task number (0 if self) *
* ACCB = Semaphore number (0 if task's semaphore) *
* IX = Number of clock tocks to delay *
* swi *
* FCB .delay. *
* return. Registers unchanged *
* *
*******************************************************************************
.delay ldaa #$FF Set up switch.
*******************************************************************************
* *
* Set up a timer for a task. This function is used to set a timer active for *
* the specified task. The timer is in clock tock units. A clock tock is a *
* system defined parameter. The timers are 16-bit timers; therefore, the *
* maximum duration of a timer is determined by the clock tock frequency. *
* *
* calling sequence: *
* *
* ACCA = Task number (0 if self) *
* ACCB = Semaphore number (0 if task's semaphore) *
* IX = Number of clock tocks in timer *
* swi *
* FCB .timer. *
* return. Registers unchanged *
* ACCA will be current task number if ACCA was = 0 *
* at the time of the ESR. *
* *
*******************************************************************************
.timer inca Set up switch: 0 = .delay., >0 = .timer.
psha Save the switch.
tstb Test for task being self.
bne notself branch if task # is not 0.
ldx curtcb Get address of current task's TCB
ldaa curtsk get the current task number.
staa ACCA,y Save it in the stack context.
bra gotask
notself ldaa #TCBLEN
mul Compute TCB address of specified task.
addd #STATLS-TCBLEN
xgdx
gotask ldab ACCA,y Get the actual task number
pshb Save the task number.
pshx Save the TCB address of the specified task.
ldaa ACCB,y Get the semaphore number to use with timer.
bne gotsema Branch if semaphore number is defined.
ldaa ACCA,y If semaphore # = 0, Use task semaphore of
adda #NNAMSEM
gotsema psha specified task. Save semaphore number.
ldd IX,y Get the timer value.
bmi savrset If timer < 0, go save the timer reset value.
clra
clrb Timer is a one-shot. Reset time = 0.
savrset pshb Save the reset time.
psha
ldd IX,y Get the timer (as specified) again.
bpl savtime See if it is cyclic or one-shot.
coma If cyclic, be sure it is positive.
negb
bcs savtime
inca
savtime ldx #ACTIVE Let IX contain the address of the active
sei timers.
tloop ldy CLINK,x Get address of next timer.
beq append If end-of-list, go append new timer.
cpd CTOCKS,y There is a timer that is active. Compare its
* timer counts with those in the new timer.
blt nsrtm If there are fewer tocks in the new timer,
* go insert the new timer in front of the
* active one.
subd CTOCKS,y If not, subtract the tocks of the active
* timer from the new timer.
ldx CLINK,x Then move down the thread.
bra tloop
nsrtm pshb Save the remaining time in the timer.
psha
subd CTOCKS,y Update the active timer in the list
coma subtracting the new timer's residual
negb from it.
bcs updt
inca
updt std CTOCKS,y Store the updated timer.
bra linkit
append pshb Save the timer.
psha
linkit ldd CLINK,x Get the address of the current active timer.
pshb
psha Save the address
ldd FREE Get the address of the next free timer block
std CLINK,x Store it in link of the active timer block.
xgdy IY now contains address of the new timer blk
ldd CLINK,y Get address of next free timer block.
std FREE Save it in pointer.
pula
pulb Get address of the current active timer.
std CLINK,y Store it in link word of the new timer block
pula
pulb
std CTOCKS,y Store timer tocks in new timer block.
pula
pulb
std CRESET,y Store timer reset value.
pulb
stab CSEMA,y Store semaphore #
pulx Get TCB address
pula
staa CTASK,y Store # of the task using the timer.
pula Get the switch
tsta
beq dlay Branch if switch set for .delay.
jmp dopend ** (V1.1 #3) **
dlay bset STATE,x _WAIT If .delay., set wait state in task.
ldx #FLGTBL-1
abx Compute address of semaphore.
ldaa CTASK,y Get the # of the task being delayed.
nega
staa 0,x Set the semaphore to WAIT state.
jmp endtsk
*******************************************************************************
* *
* Purge a timer or timers. This function is used to purge one timer for a *
* given task and semaphore combination or all of the timers for a given task. *
* Each timer so purged has its associated semaphore reset to the PENDing *
* state. *
* *
* calling sequence: *
* *
* ACCA = Task number (0 if self) *
* ACCB = Semaphore number (0 if task's semaphore) *
* IX0-7= Switch: 0 = purge all timers for given task *
* >0 = purge timer matching task and sema *
* swi *
* FCB .purge. *
* return. Registers unchanged *
* *
*******************************************************************************
.purge tstb
bne gotsk If ACCB > 0, task is specific.
ldab curtsk Otherwise, use current task.
gotsk std temp Save task and semaphore numbers.
chksema clra
tst IXL,y Test the switch for type of purge.
beq beginp Branch if switch = 0.
ldaa temp Otherwise get the semaphore number to match.
bne beginp Branch if it is defined.
ldaa temp+1 Otherwise use the task semaphore.
adda #NNAMSEM
beginp ldab temp+1 Load the task number to be matched.
ldx #ACTIVE Set up pointer to 1st active timer.
sei Interrupts off during this next phase.
ploop ldy CLINK,x Set IY = address of next active timer.
beq endpurg If end-of-list, purge is complete.
cmpb CTASK,y Compare task # in active timer to object #.
beq sametsk Branch if both task numbers are equal.
chknxt ldx CLINK,x If not equal, walk list to next active timer
bra ploop Keep looking.
sametsk tsta Tasks are equal. Test if semaphore # = 0.
beq remove Branch if semaphore # = 0.
cmpa CSEMA,y If semaphore > 0, Check it against semaphore
bne chknxt in active timer. Branch if not the same.
remove pshb
psha Save semaphore and task number.
pshx Save IX for a moment.
ldx #FLGTBL-1
ldab CSEMA,y
abx Compute address of timer's semaphore.
ldaa #_PEND
staa 0,x Set timer's semaphore back to PENDing.
pulx Restore IX
ldd CLINK,y Get the pointer to next active timer block.
std CLINK,x Store it in link word of pointer to current
* active timer.
*** (V1.4 #3) *** |
beq prgexit |
ldd CTOCKS,y Update the next timer in link |
*** (V1.5 #1) *** +
pshy Save y +
ldy CLINK,y |
addd CTOCKS,y |
std CTOCKS,y |
puly Restore Y +
prgexit ldd FREE |
std CLINK,y Make current timer block the first free one.
xgdy
std FREE
pula
pulb Restore task and semaphore numbers.
tsta Test the semaphore number.
beq ploop Branch if semaphore = 0. (purge all timers)
endpurg jmp endtsk Otherwise that's all.